package data

import (
	"context"
	"errors"
	"fmt"
	"medicine/internal/biz"
	"time"

	"gorm.io/gorm"
)

// seckillProductRepo 秒杀商品仓储实现，负责秒杀商品相关数据库操作
// 实现了 SeckillProductRepo 接口
type seckillProductRepo struct {
	data *DataRepo // 数据层依赖，包含数据库和缓存
}

func NewSeckillProductRepo(data *DataRepo) biz.SeckillProductRepo {
	return &seckillProductRepo{data: data}
}

// List 分页查询秒杀商品列表
func (r *seckillProductRepo) List(ctx context.Context, page, pageSize int) ([]*biz.SeckillProduct, int, error) {
	var sps []*biz.SeckillProduct
	var total int64
	db := r.data.DB.WithContext(ctx)
	db.Model(&biz.SeckillProduct{}).Count(&total)
	db = db.Offset((page - 1) * pageSize).Limit(pageSize)
	if err := db.Find(&sps).Error; err != nil {
		return nil, 0, err
	}
	return sps, int(total), nil
}

// Get 查询秒杀商品详情
func (r *seckillProductRepo) Get(ctx context.Context, id int64) (*biz.SeckillProduct, error) {
	var sp biz.SeckillProduct
	if err := r.data.DB.WithContext(ctx).First(&sp, id).Error; err != nil {
		return nil, err
	}
	return &sp, nil
}

// Exists 校验商品是否已参与活动
func (r *seckillProductRepo) Exists(ctx context.Context, productId, eventId int64) bool {
	var count int64
	r.data.DB.WithContext(ctx).Model(&biz.SeckillProduct{}).
		Where("product_id = ? AND seckill_event_id = ?", productId, eventId).Count(&count)
	return count > 0
}

// CreateWithStock 创建秒杀商品并扣减商品库存（事务）
func (r *seckillProductRepo) CreateWithStock(ctx context.Context, sp *biz.SeckillProduct, stock int) (*biz.SeckillProduct, error) {
	return sp, r.data.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// 扣减商品库存
		res := tx.Model(&biz.Product{}).
			Where("id = ? AND stock >= ?", sp.ProductID, stock).
			Update("stock", gorm.Expr("stock - ?", stock))
		if res.RowsAffected == 0 {
			return errors.New("商品库存不足")
		}
		// 创建秒杀商品
		if err := tx.Create(sp).Error; err != nil {
			return err
		}

		// 仅当距离开始时间小于等于30分钟才预热到 Redis
		if time.Until(sp.SeckillStart) <= 30*time.Minute {
			if err := r.PreheatSeckillProduct(ctx, sp, sp.SeckillEnd); err != nil {
				fmt.Printf("预热秒杀商品到Redis失败: 商品ID=%d, 错误=%v\n", sp.ID, err)
			}
		}

		return nil
	})
}

// GetProduct 查询商品详情
func (r *seckillProductRepo) GetProduct(ctx context.Context, productId int64) (*biz.Product, error) {
	var p biz.Product
	if err := r.data.DB.WithContext(ctx).First(&p, productId).Error; err != nil {
		return nil, err
	}
	return &p, nil
}

// PreheatSeckillProduct 预热秒杀商品到 Redis，设置库存和商品详情
func (r *seckillProductRepo) PreheatSeckillProduct(ctx context.Context, sp *biz.SeckillProduct, endTime time.Time) error {
	stockKey := fmt.Sprintf("seckill:stock:%d", sp.ID)
	infoKey := fmt.Sprintf("seckill:info:%d", sp.ID)
	// 设置库存
	if err := r.data.Redis.Set(ctx, stockKey, sp.SeckillStock, time.Until(endTime)).Err(); err != nil {
		return err
	}
	// 设置商品详情
	info := map[string]interface{}{
		"product_id":       sp.ProductID,
		"seckill_price":    sp.SeckillPrice,
		"seckill_start":    sp.SeckillStart.Format("2006-01-02 15:04:05"),
		"seckill_end":      sp.SeckillEnd.Format("2006-01-02 15:04:05"),
		"seckill_event_id": sp.SeckillEventID,
	}
	if err := r.data.Redis.HMSet(ctx, infoKey, info).Err(); err != nil {
		return err
	}
	// 设置过期时间
	r.data.Redis.ExpireAt(ctx, stockKey, endTime)
	r.data.Redis.ExpireAt(ctx, infoKey, endTime)
	return nil
}

// SyncAllSeckillProducts 批量同步即将开始或正在进行的秒杀商品
func (r *seckillProductRepo) PreheatAllSeckillProducts(ctx context.Context) error {
	now := time.Now()
	// 可配置的预热时间窗口，默认10分钟
	preheatWindow := 10 * time.Minute
	preheatTime := now.Add(preheatWindow)

	var sps []*biz.SeckillProduct
	// 只预热即将开始的商品：开始时间在预热窗口内，且还未开始
	if err := r.data.DB.WithContext(ctx).
		Where("seckill_start > ? AND seckill_start <= ?", now, preheatTime).
		Order("seckill_start ASC").
		Find(&sps).Error; err != nil {
		return err
	}

	fmt.Printf("定时预热任务: 当前时间=%s, 预热窗口=%s, 找到%d个商品需要预热\n",
		now.Format("2006-01-02 15:04:05"), preheatWindow, len(sps))

	for _, sp := range sps {
		stockKey := fmt.Sprintf("seckill:stock:%d", sp.ID)
		exists, err := r.data.Redis.Exists(ctx, stockKey).Result()
		if err != nil {
			fmt.Printf("检查Redis库存key失败: 商品ID=%d, err: %v\n", sp.ID, err)
			continue
		}
		if exists > 0 {
			fmt.Printf("Redis已存在库存数据，跳过预热: 商品ID=%d\n", sp.ID)
			continue
		}
		timeToStart := sp.SeckillStart.Sub(now)
		fmt.Printf("预热即将开始的商品: ID=%d, 距离开始=%s\n", sp.ID, timeToStart.Round(time.Minute))
		if err := r.PreheatSeckillProduct(ctx, sp, sp.SeckillEnd); err != nil {
			fmt.Printf("预热商品到Redis失败: 商品ID=%d, err: %v\n", sp.ID, err)
		}
	}
	return nil
}

// ListDetail 分页查询秒杀商品列表（带商品和活动信息）
func (r *seckillProductRepo) ListDetail(ctx context.Context, page, pageSize int) ([]*biz.SeckillProductDetail, int, error) {
	var sps []*biz.SeckillProduct
	var total int64
	db := r.data.DB.WithContext(ctx)
	db.Model(&biz.SeckillProduct{}).Count(&total)
	db = db.Offset((page - 1) * pageSize).Limit(pageSize)
	if err := db.Find(&sps).Error; err != nil {
		return nil, 0, err
	}
	var details []*biz.SeckillProductDetail
	for _, sp := range sps {
		product, _ := r.GetProduct(ctx, sp.ProductID)
		var eventName string
		var event biz.SeckillEvent
		r.data.DB.WithContext(ctx).First(&event, sp.SeckillEventID)
		eventName = event.Name
		status := "未开始"
		now := time.Now()
		if now.After(sp.SeckillStart) && now.Before(sp.SeckillEnd) {
			status = "进行中"
		} else if now.After(sp.SeckillEnd) {
			status = "已结束"
		}
		img := ""
		if product != nil {
			img = product.Description // 假设用描述字段做图片占位
		}
		details = append(details, &biz.SeckillProductDetail{
			SeckillProduct: *sp,
			ProductName:    product.Name,
			ProductImage:   img,
			OriginPrice:    product.Price,
			EventName:      eventName,
			Status:         status,
		})
	}
	return details, int(total), nil
}

// GetDetail 查询秒杀商品详情（带商品和活动信息）
func (r *seckillProductRepo) GetDetail(ctx context.Context, id int64) (*biz.SeckillProductDetail, error) {
	sp, err := r.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	product, _ := r.GetProduct(ctx, sp.ProductID)
	var eventName string
	var event biz.SeckillEvent
	r.data.DB.WithContext(ctx).First(&event, sp.SeckillEventID)
	eventName = event.Name
	status := "未开始"
	now := time.Now()
	if now.After(sp.SeckillStart) && now.Before(sp.SeckillEnd) {
		status = "进行中"
	} else if now.After(sp.SeckillEnd) {
		status = "已结束"
	}
	img := ""
	if product != nil {
		img = product.Description // 假设用描述字段做图片占位
	}
	return &biz.SeckillProductDetail{
		SeckillProduct: *sp,
		ProductName:    product.Name,
		ProductImage:   img,
		OriginPrice:    product.Price,
		EventName:      eventName,
		Status:         status,
	}, nil
}

// seckillEventRepo 秒杀活动仓储实现，负责活动相关数据库操作
// 实现了 SeckillEventRepo 接口
type seckillEventRepo struct {
	data *DataRepo // 数据层依赖，包含数据库和缓存
}

func NewSeckillEventRepo(data *DataRepo) biz.SeckillEventRepo {
	return &seckillEventRepo{data: data}
}

// Create 创建活动
func (r *seckillEventRepo) Create(ctx context.Context, e *biz.SeckillEvent) (*biz.SeckillEvent, error) {
	if err := r.data.DB.WithContext(ctx).Create(e).Error; err != nil {
		return nil, err
	}
	return e, nil
}

// List 分页查询活动列表
func (r *seckillEventRepo) List(ctx context.Context, page, pageSize int) ([]*biz.SeckillEvent, int, error) {
	var events []*biz.SeckillEvent
	var total int64
	db := r.data.DB.WithContext(ctx)
	db.Model(&biz.SeckillEvent{}).Count(&total)
	db = db.Offset((page - 1) * pageSize).Limit(pageSize)
	if err := db.Find(&events).Error; err != nil {
		return nil, 0, err
	}
	return events, int(total), nil
}

// Get 查询活动详情
func (r *seckillEventRepo) Get(ctx context.Context, id int64) (*biz.SeckillEvent, error) {
	var e biz.SeckillEvent
	if err := r.data.DB.WithContext(ctx).First(&e, id).Error; err != nil {
		return nil, err
	}
	return &e, nil
}

// NameExists 校验活动名唯一性
func (r *seckillEventRepo) NameExists(ctx context.Context, name string) bool {
	var count int64
	r.data.DB.WithContext(ctx).Model(&biz.SeckillEvent{}).Where("name = ?", name).Count(&count)
	return count > 0
}

// TimeConflict 校验活动时间冲突
func (r *seckillEventRepo) TimeConflict(ctx context.Context, start, end time.Time) bool {
	var count int64
	r.data.DB.WithContext(ctx).Model(&biz.SeckillEvent{}).
		Where("start_time < ? AND end_time > ?", end, start).Count(&count)
	return count > 0
}

// seckillOrderRepo 秒杀订单仓储实现，负责订单相关数据库操作
// 实现了 SeckillOrderRepo 接口
type seckillOrderRepo struct {
	data *DataRepo // 数据层依赖，包含数据库和缓存
}

func NewSeckillOrderRepo(data *DataRepo) biz.SeckillOrderRepo {
	return &seckillOrderRepo{data: data}
}

// GetAlipayService 返回支付宝服务实例
func (r *seckillOrderRepo) GetAlipayService() interface {
	GeneratePayURL(ctx context.Context, orderId string, subject string, amount float64) (string, error)
} {
	return r.data.Alipay
}

// Redis 扣减虚拟库存，成功返回true，失败返回false
func (r *seckillOrderRepo) decrRedisStock(ctx context.Context, seckillProductID int64, quantity int) (bool, error) {
	stockKey := "seckill:stock:" + fmt.Sprint(seckillProductID)
	res := r.data.Redis.Eval(ctx, `
		local stock = redis.call('GET', KEYS[1])
		if not stock then return -1 end
		stock = tonumber(stock)
		if stock < tonumber(ARGV[1]) then return -2 end
		stock = stock - tonumber(ARGV[1])
		redis.call('SET', KEYS[1], stock)
		return stock
	`, []string{stockKey}, quantity)
	if res.Err() != nil {
		return false, res.Err()
	}
	val, ok := res.Val().(int64)
	if !ok {
		return false, fmt.Errorf("redis返回类型错误")
	}
	if val == -1 {
		return false, fmt.Errorf("虚拟库存不存在")
	}
	if val == -2 {
		return false, fmt.Errorf("虚拟库存不足")
	}
	return true, nil
}

// Create 创建订单，生成支付链接
func (r *seckillOrderRepo) Create(ctx context.Context, o *biz.SeckillOrder) (*biz.SeckillOrder, error) {
	// 这里o.OrderNo和o.TotalPrice应由上层业务赋值，gorm自动保存
	ok, err := r.decrRedisStock(ctx, o.SeckillProductID, o.Quantity)
	if err != nil || !ok {
		return nil, fmt.Errorf("扣减虚拟库存失败: %v", err)
	}
	if err = r.data.DB.WithContext(ctx).Create(o).Error; err != nil {
		_ = r.data.MQ.PublishRollback(ctx, StockRollbackMsg{
			SeckillProductID: o.SeckillProductID,
			Quantity:         o.Quantity,
		})
		return nil, err
	}
	return o, nil
}

// incrRedisStock 回滚Redis库存
func (r *seckillOrderRepo) incrRedisStock(ctx context.Context, seckillProductID int64, quantity int) error {
	stockKey := "seckill:stock:" + fmt.Sprint(seckillProductID)
	return r.data.Redis.IncrBy(ctx, stockKey, int64(quantity)).Err()
}

// Get 查询订单详情
func (r *seckillOrderRepo) Get(ctx context.Context, id int64) (*biz.SeckillOrder, error) {
	// gorm会自动映射order_no和total_price字段
	var o biz.SeckillOrder
	if err := r.data.DB.WithContext(ctx).First(&o, id).Error; err != nil {
		return nil, err
	}
	return &o, nil
}

// GetByOrderNo 根据订单编号查询订单
func (r *seckillOrderRepo) GetByOrderNo(ctx context.Context, orderNo string) (*biz.SeckillOrder, error) {
	var o biz.SeckillOrder
	if err := r.data.DB.WithContext(ctx).Where("order_no = ?", orderNo).First(&o).Error; err != nil {
		return nil, err
	}
	return &o, nil
}

// List 分页查询订单列表
func (r *seckillOrderRepo) List(ctx context.Context, userId int64, page, pageSize int) ([]*biz.SeckillOrder, int, error) {
	var orders []*biz.SeckillOrder
	var total int64
	db := r.data.DB.WithContext(ctx).Model(&biz.SeckillOrder{})
	if userId > 0 {
		db = db.Where("user_id = ?", userId)
	}
	db.Count(&total)
	db = db.Offset((page - 1) * pageSize).Limit(pageSize)
	if err := db.Find(&orders).Error; err != nil {
		return nil, 0, err
	}
	return orders, int(total), nil
}

// RollbackStock 根据订单ID回滚Redis库存，幂等处理
func (r *seckillOrderRepo) RollbackStock(ctx context.Context, orderId int64) error {
	var o biz.SeckillOrder
	if err := r.data.DB.WithContext(ctx).First(&o, orderId).Error; err != nil {
		return err
	}
	// 幂等性：只有已取消的订单才回滚库存
	if o.Status != "已取消" {
		return nil
	}
	// 回滚 Redis 库存
	err := r.incrRedisStock(ctx, o.SeckillProductID, o.Quantity)
	if err != nil {
		fmt.Printf("[库存回滚] Redis回滚失败, 订单ID=%d, err=%v\n", orderId, err)
		return err
	}
	return nil
}

// RollbackStockForce 强制回滚Redis库存，不检查订单状态（用于定时任务等场景）
func (r *seckillOrderRepo) RollbackStockForce(ctx context.Context, orderId int64) error {
	var o biz.SeckillOrder
	if err := r.data.DB.WithContext(ctx).First(&o, orderId).Error; err != nil {
		return err
	}
	// 强制回滚 Redis 库存，不检查状态
	err := r.incrRedisStock(ctx, o.SeckillProductID, o.Quantity)
	if err != nil {
		fmt.Printf("[库存回滚] Redis回滚失败, 订单ID=%d, err=%v\n", orderId, err)
		return err
	}
	return nil
}

// UpdateOrderStatus 根据订单编号更新订单状态和支付时间
func (r *seckillOrderRepo) UpdateOrderStatus(ctx context.Context, orderNo string, status string) error {
	return r.data.DB.WithContext(ctx).Model(&biz.SeckillOrder{}).
		Where("order_no = ?", orderNo).
		Updates(map[string]interface{}{
			"status":  status,
			"paid_at": time.Now(),
		}).Error
}
